// T3DLIB3.CPP - Game Engine Part III, sound & music

// INCLUDES ///////////////////////////////////////////////

#define WIN32_LEAN_AND_MEAN  

#include <windows.h>   // include important windows stuff
#include <windowsx.h> 
#include <mmsystem.h>
#include <objbase.h>
#include <iostream> // include important C/C++ stuff
#include <conio.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
#include <io.h>
#include <fcntl.h>
#include <direct.h>
#include <wchar.h>

#include <ddraw.h>  // directX includes
#include <dsound.h>
#include <dmksctrl.h>
#include "dmusici.h"
#include <dmusicc.h>
#include "dmusicf.h"


#include "T3DLIB3.H"

// DEFINES ////////////////////////////////////////////////


// TYPES //////////////////////////////////////////////////

// PROTOTYPES /////////////////////////////////////////////

// EXTERNALS /////////////////////////////////////////////

extern HWND main_window_handle;     // access to main window handle in main module

// GLOBALS ////////////////////////////////////////////////

// directsound stuff
LPDIRECTSOUND		lpds = NULL;    // directsound interface pointer
DSBUFFERDESC		dsbd;           // directsound description
DSCAPS				dscaps;         // directsound caps
HRESULT				dsresult;       // general directsound result
DSBCAPS				dsbcaps;        // directsound buffer caps
LPDIRECTSOUNDBUFFER	lpdsbprimary = NULL;   // the primary mixing buffer
pcm_sound			sound_fx[MAX_SOUNDS];    // the array of secondary sound buffers

WAVEFORMATEX		pcmwf;          // generic waveformat structure

// direct music globals
IDirectMusicPerformance    *dm_perf = NULL;    // the directmusic performance manager 
IDirectMusicLoader         *dm_loader = NULL;  // the directmusic loader

// this hold all the directmusic midi objects
DMUSIC_MIDI                dm_midi[DM_NUM_SEGMENTS];
int dm_active_id = -1;     // currently active midi segment

// FUNCTIONS //////////////////////////////////////////////

int DSound_Load_WAV(const char *filename, int control_flags)
{
	// this function loads a .wav file, sets up the directsound 
	// buffer and loads the data into memory, the function returns 
	// the id number of the sound


	HMMIO 			hwav;    // handle to wave file
	MMCKINFO		parent,  // parent chunk
		child;   // child chunk
	WAVEFORMATEX    wfmtx;   // wave format structure

	int	sound_id = -1,       // id of sound to be loaded
		index;               // looping variable

	UCHAR *snd_buffer,       // temporary sound buffer to hold voc data
		*audio_ptr_1 = NULL, // data ptr to first write buffer 
		*audio_ptr_2 = NULL; // data ptr to second write buffer

	DWORD audio_length_1 = 0,  // length of first write buffer
		audio_length_2 = 0;  // length of second write buffer

  // step one: are there any open id's ?
	for (index = 0; index < MAX_SOUNDS; index++)
	{
		// make sure this sound is unused
		if (sound_fx[index].state == SOUND_NULL)
		{
			sound_id = index;
			break;
		} // end if

	} // end for index

// did we get a free id?
	if (sound_id == -1)
		return(-1);

	// set up chunk info structure
	parent.ckid = (FOURCC)0;
	parent.cksize = 0;
	parent.fccType = (FOURCC)0;
	parent.dwDataOffset = 0;
	parent.dwFlags = 0;

	// copy data
	child = parent;

	// open the WAV file
	if ((hwav = mmioOpenA((LPSTR)filename, NULL, MMIO_READ | MMIO_ALLOCBUF)) == NULL)
		return(-1);

	// descend into the RIFF 
	parent.fccType = mmioFOURCC('W', 'A', 'V', 'E');

	if (mmioDescend(hwav, &parent, NULL, MMIO_FINDRIFF))
	{
		// close the file
		mmioClose(hwav, 0);

		// return error, no wave section
		return(-1);
	} // end if

// descend to the WAVEfmt 
	child.ckid = mmioFOURCC('f', 'm', 't', ' ');

	if (mmioDescend(hwav, &child, &parent, 0))
	{
		// close the file
		mmioClose(hwav, 0);

		// return error, no format section
		return(-1);
	} // end if

// now read the wave format information from file
	if (mmioRead(hwav, (char *)&wfmtx, sizeof(wfmtx)) != sizeof(wfmtx))
	{
		// close file
		mmioClose(hwav, 0);

		// return error, no wave format data
		return(-1);
	} // end if

// make sure that the data format is PCM
	if (wfmtx.wFormatTag != WAVE_FORMAT_PCM)
	{
		// close the file
		mmioClose(hwav, 0);

		// return error, not the right data format
		return(-1);
	} // end if

// now ascend up one level, so we can access data chunk
	if (mmioAscend(hwav, &child, 0))
	{
		// close file
		mmioClose(hwav, 0);

		// return error, couldn't ascend
		return(-1);
	} // end if

 // descend to the data chunk 
	child.ckid = mmioFOURCC('d', 'a', 't', 'a');

	if (mmioDescend(hwav, &child, &parent, MMIO_FINDCHUNK))
	{
		// close file
		mmioClose(hwav, 0);

		// return error, no data
		return(-1);
	} // end if

// finally!!!! now all we have to do is read the data in and
// set up the directsound buffer

// allocate the memory to load sound data
	snd_buffer = (UCHAR *)malloc(child.cksize);

	// read the wave data 
	mmioRead(hwav, (char *)snd_buffer, child.cksize);

	// close the file
	mmioClose(hwav, 0);

	// set rate and size in data structure
	sound_fx[sound_id].rate = wfmtx.nSamplesPerSec;
	sound_fx[sound_id].size = child.cksize;
	sound_fx[sound_id].state = SOUND_LOADED;

	// set up the format data structure
	memset(&pcmwf, 0, sizeof(WAVEFORMATEX));

	pcmwf.wFormatTag = WAVE_FORMAT_PCM;  // pulse code modulation
	pcmwf.nChannels = 1;                // mono 
	pcmwf.nSamplesPerSec = 11025;            // always this rate
	pcmwf.nBlockAlign = 1;
	pcmwf.nAvgBytesPerSec = pcmwf.nSamplesPerSec * pcmwf.nBlockAlign;
	pcmwf.wBitsPerSample = 8;
	pcmwf.cbSize = 0;

	// prepare to create sounds buffer
	dsbd.dwSize = sizeof(DSBUFFERDESC);
	dsbd.dwFlags = control_flags | DSBCAPS_STATIC | DSBCAPS_LOCSOFTWARE;
	dsbd.dwBufferBytes = child.cksize;
	dsbd.lpwfxFormat = &pcmwf;

	// create the sound buffer
	if (FAILED(lpds->CreateSoundBuffer(&dsbd, &sound_fx[sound_id].dsbuffer, NULL)))
	{
		// release memory
		free(snd_buffer);

		// return error
		return(-1);
	} // end if

 // copy data into sound buffer
	if (FAILED(sound_fx[sound_id].dsbuffer->Lock(0,
		child.cksize,
		(void **)&audio_ptr_1,
		&audio_length_1,
		(void **)&audio_ptr_2,
		&audio_length_2,
		DSBLOCK_FROMWRITECURSOR)))
		return(0);

	// copy first section of circular buffer
	memcpy(audio_ptr_1, snd_buffer, audio_length_1);

	// copy last section of circular buffer
	memcpy(audio_ptr_2, (snd_buffer + audio_length_1), audio_length_2);

	// unlock the buffer
	if (FAILED(sound_fx[sound_id].dsbuffer->Unlock(audio_ptr_1,
		audio_length_1,
		audio_ptr_2,
		audio_length_2)))
		return(0);

	// release the temp buffer
	free(snd_buffer);

	// return id
	return(sound_id);

} // end DSound_Load_WAV

///////////////////////////////////////////////////////////

int DSound_Replicate_Sound(int source_id)
{
	// this function replicates the sent sound and sends back the
	// id of the replicated sound, you would use this function
	// to make multiple copies of a gunshot or something that
	// you want to play multiple times simulataneously, but you
	// only want to load once

	if (source_id != -1)
	{
		// duplicate the sound buffer
		// first hunt for an open id

		for (int id = 0; id < MAX_SOUNDS; id++)
		{
			// is this sound open?
			if (sound_fx[id].state == SOUND_NULL)
			{
				// first make an identical copy
				sound_fx[id] = sound_fx[source_id];

				// now actually replicate the directsound buffer
				if (FAILED(lpds->DuplicateSoundBuffer(sound_fx[source_id].dsbuffer,
					&sound_fx[id].dsbuffer)))
				{
					// reset sound to NULL
					sound_fx[id].dsbuffer = NULL;
					sound_fx[id].state = SOUND_NULL;

					// return error
					return(-1);
				} // end if

			// now fix up id
				sound_fx[id].id = id;

				// return replicated sound
				return(id);

			} // end if found

		} // end for id

	} // end if
	else
		return(-1);

	// else failure
	return(-1);

} // end DSound_Replicate_Sound

//////////////////////////////////////////////////////////

int DSound_Init(void)
{
	// this function initializes the sound system
	static int first_time = 1; // used to track the first time the function
							   // is entered

	// test for very first time
	if (first_time)
	{
		// clear everything out
		memset(sound_fx, 0, sizeof(pcm_sound)*MAX_SOUNDS);

		// reset first time
		first_time = 0;

		// create a directsound object
		if (FAILED(DirectSoundCreate(NULL, &lpds, NULL)))
			return(0);

		// set cooperation level
		if (FAILED(lpds->SetCooperativeLevel((HWND)main_window_handle, DSSCL_NORMAL)))
			return(0);

	} // end if

// initialize the sound fx array
	for (int index = 0; index < MAX_SOUNDS; index++)
	{
		// test if this sound has been loaded
		if (sound_fx[index].dsbuffer)
		{
			// stop the sound
			sound_fx[index].dsbuffer->Stop();

			// release the buffer
			sound_fx[index].dsbuffer->Release();

		} // end if

	// clear the record out
		memset(&sound_fx[index], 0, sizeof(pcm_sound));

		// now set up the fields
		sound_fx[index].state = SOUND_NULL;
		sound_fx[index].id = index;

	} // end for index

// return sucess
	return(1);

} // end DSound_Init

///////////////////////////////////////////////////////////

int DSound_Shutdown(void)
{
	// this function releases all the memory allocated and the directsound object
	// itself

	// first turn all sounds off
	DSound_Stop_All_Sounds();

	// now release all sound buffers
	for (int index = 0; index < MAX_SOUNDS; index++)
		if (sound_fx[index].dsbuffer)
			sound_fx[index].dsbuffer->Release();

	// now release the directsound interface itself
	if (lpds)
		lpds->Release();

	// return success
	return(1);

} // end DSound_Shutdown

///////////////////////////////////////////////////////////

int DSound_Play(int id, int flags, int volume, int rate, int pan)
{
	// this function plays a sound, the only parameter that 
	// works is the flags which can be 0 to play once or
	// DSBPLAY_LOOPING

	if (sound_fx[id].dsbuffer)
	{
		// reset position to start
		if (FAILED(sound_fx[id].dsbuffer->SetCurrentPosition(0)))
			return(0);

		// play sound
		if (FAILED(sound_fx[id].dsbuffer->Play(0, 0, flags)))
			return(0);
	} // end if

// return success
	return(1);

} // end DSound_Play

///////////////////////////////////////////////////////////

int DSound_Set_Volume(int id, int vol)
{
	// this function sets the volume on a sound 0-100

	if (sound_fx[id].dsbuffer->SetVolume(DSVOLUME_TO_DB(vol)) != DS_OK)
		return(0);

	// return success
	return(1);

} // end DSound_Set_Volume

///////////////////////////////////////////////////////////

int DSound_Set_Freq(int id, int freq)
{
	// this function sets the playback rate

	if (sound_fx[id].dsbuffer->SetFrequency(freq) != DS_OK)
		return(0);

	// return success
	return(1);

} // end DSound_Set_Freq

///////////////////////////////////////////////////////////

int DSound_Set_Pan(int id, int pan)
{
	// this function sets the pan, -10,000 to 10,0000

	if (sound_fx[id].dsbuffer->SetPan(pan) != DS_OK)
		return(0);

	// return success
	return(1);

} // end DSound_Set_Pan

////////////////////////////////////////////////////////////

int DSound_Stop_Sound(int id)
{
	// this function stops a sound from playing
	if (sound_fx[id].dsbuffer)
	{
		sound_fx[id].dsbuffer->Stop();
		sound_fx[id].dsbuffer->SetCurrentPosition(0);
	} // end if

 // return success
	return(1);

} // end DSound_Stop_Sound

///////////////////////////////////////////////////////////

int DSound_Delete_All_Sounds(void)
{
	// this function deletes all the sounds

	for (int index = 0; index < MAX_SOUNDS; index++)
		DSound_Delete_Sound(index);

	// return success always
	return(1);

} // end DSound_Delete_All_Sounds

///////////////////////////////////////////////////////////

int DSound_Delete_Sound(int id)
{
	// this function deletes a single sound and puts it back onto the available list

	// first stop it
	if (!DSound_Stop_Sound(id))
		return(0);

	// now delete it
	if (sound_fx[id].dsbuffer)
	{
		// release the com object
		sound_fx[id].dsbuffer->Release();
		sound_fx[id].dsbuffer = NULL;

		// return success
		return(1);
	} // end if

 // return success
	return(1);

} // end DSound_Delete_Sound

///////////////////////////////////////////////////////////

int DSound_Stop_All_Sounds(void)
{
	// this function stops all sounds

	for (int index = 0; index < MAX_SOUNDS; index++)
		DSound_Stop_Sound(index);

	// return success
	return(1);

} // end DSound_Stop_All_Sounds

///////////////////////////////////////////////////////////

int DSound_Status_Sound(int id)
{
	// this function returns the status of a sound
	if (sound_fx[id].dsbuffer)
	{
		ULONG status;

		// get the status
		sound_fx[id].dsbuffer->GetStatus(&status);

		// return the status
		return(status);

	} // end if
	else // total failure
		return(-1);

} // end DSound_Status_Sound

///////////////////////////////////////////////////////////

int DMusic_Load_MIDI(const char *filename)
{
	// this function loads a midi segment

	DMUS_OBJECTDESC ObjDesc;
	HRESULT hr;
	IDirectMusicSegment* pSegment = NULL;

	int index; // loop var

	// look for open slot for midi segment
	int id = -1;

	for (index = 0; index < DM_NUM_SEGMENTS; index++)
	{
		// is this one open
		if (dm_midi[index].state == MIDI_NULL)
		{
			// validate id, but don't validate object until loaded
			id = index;
			break;
		} // end if

	} // end for index

// found good id?
	if (id == -1)
		return(-1);

	// get current working directory
	char szDir[_MAX_PATH];
	WCHAR wszDir[_MAX_PATH];

	if (_getcwd(szDir, _MAX_PATH) == NULL)
	{
		return(-1);;
	} // end if

	MULTI_TO_WIDE(wszDir, szDir);

	// tell the loader were to look for files
	hr = dm_loader->SetSearchDirectory(GUID_DirectMusicAllTypes, wszDir, FALSE);

	if (FAILED(hr))
	{
		return (-1);
	} // end if

 // convert filename to wide string
	WCHAR wfilename[_MAX_PATH];
	MULTI_TO_WIDE(wfilename, filename);

	// setup object description
	DD_INIT_STRUCT(ObjDesc);
	ObjDesc.guidClass = CLSID_DirectMusicSegment;
	wcscpy(ObjDesc.wszFileName, wfilename);
	ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME;

	// load the object and query it for the IDirectMusicSegment interface
	// This is done in a single call to IDirectMusicLoader::GetObject
	// note that loading the object also initializes the tracks and does 
	// everything else necessary to get the MIDI data ready for playback.

	hr = dm_loader->GetObject(&ObjDesc, IID_IDirectMusicSegment, (void**)&pSegment);

	if (FAILED(hr))
		return(-1);

	// ensure that the segment plays as a standard MIDI file
	// you now need to set a parameter on the band track
	// Use the IDirectMusicSegment::SetParam method and let 
	// DirectMusic find the trackby passing -1 (or 0xFFFFFFFF) in the dwGroupBits method parameter.

	hr = pSegment->SetParam(GUID_StandardMIDIFile, -1, 0, 0, (void*)dm_perf);

	if (FAILED(hr))
		return(-1);

	// This step is necessary because DirectMusic handles program changes and 
	// bank selects differently for standard MIDI files than it does for MIDI 
	// content authored specifically for DirectMusic. 
	// The GUID_StandardMIDIFile parameter must be set before the instruments are downloaded. 

	// The next step is to download the instruments. 
	// This is necessary even for playing a simple MIDI file 
	// because the default software synthesizer needs the DLS data 
	// for the General MIDI instrument set
	// If you skip this step, the MIDI file will play silently.
	// Again, you call SetParam on the segment, this time specifying the GUID_Download parameter:

	hr = pSegment->SetParam(GUID_Download, -1, 0, 0, (void*)dm_perf);

	if (FAILED(hr))
		return(-1);

	// at this point we have MIDI loaded and a valid object

	dm_midi[id].dm_segment = pSegment;
	dm_midi[id].dm_segstate = NULL;
	dm_midi[id].state = MIDI_LOADED;

	// return id
	return(id);

} // end DMusic_Load_MIDI

//////////////////////////////////////////////////////////

int DMusic_Play(int id)
{
	// play sound based on id

	if (dm_midi[id].dm_segment && dm_midi[id].state != MIDI_NULL)
	{
		// if there is an active midi then stop it
		if (dm_active_id != -1)
			DMusic_Stop(dm_active_id);

		// play segment and force tracking of state variable
		dm_perf->PlaySegment(dm_midi[id].dm_segment, 0, 0, &dm_midi[id].dm_segstate);
		dm_midi[id].state = MIDI_PLAYING;

		// set the active midi segment
		dm_active_id = id;
		return(1);
	}  // end if
	else
		return(0);

} // end DMusic_Play

//////////////////////////////////////////////////////////

int DMusic_Stop(int id)
{
	// stop a midi segment
	if (dm_midi[id].dm_segment && dm_midi[id].state != MIDI_NULL)
	{
		// play segment and force tracking of state variable
		dm_perf->Stop(dm_midi[id].dm_segment, NULL, 0, 0);
		dm_midi[id].state = MIDI_STOPPED;

		// reset active id
		dm_active_id = -1;

		return(1);
	}  // end if
	else
		return(0);

} // end DMusic_Stop

///////////////////////////////////////////////////////////

int DMusic_Delete_MIDI(int id)
{
	// this function deletes one MIDI segment

	// Unload instruments this will cause silence.
	// CloseDown unloads all instruments, so this call is also not 
	// strictly necessary.
	if (dm_midi[id].dm_segment)
	{
		dm_midi[id].dm_segment->SetParam(GUID_Unload, -1, 0, 0, (void*)dm_perf);

		// Release the segment and set to null
		dm_midi[id].dm_segment->Release();
		dm_midi[id].dm_segment = NULL;
		dm_midi[id].dm_segstate = NULL;
		dm_midi[id].state = MIDI_NULL;
	} // end if

	return(1);

} // end DMusic_Delete_MIDI

//////////////////////////////////////////////////////////

int DMusic_Delete_All_MIDI(void)
{
	// delete all the MIDI 
	int index; // loop var

	// free up all the segments
	for (index = 0; index < DM_NUM_SEGMENTS; index++)
	{
		// Unload instruments this will cause silence.
		// CloseDown unloads all instruments, so this call is also not 
		// strictly necessary.
		if (dm_midi[index].dm_segment)
		{
			dm_midi[index].dm_segment->SetParam(GUID_Unload, -1, 0, 0, (void*)dm_perf);

			// Release the segment and set to null
			dm_midi[index].dm_segment->Release();
			dm_midi[index].dm_segment = NULL;
			dm_midi[index].dm_segstate = NULL;
			dm_midi[index].state = MIDI_NULL;
		} // end if

	} // end for index

	return(1);

} // end DMusic_Delete_All_MIDI

//////////////////////////////////////////////////////////

int DMusic_Status_MIDI(int id)
{
	// this checks the status of a midi segment

	if (dm_midi[id].dm_segment && dm_midi[id].state != MIDI_NULL)
	{
		// get the status and translate to our defines
		if (dm_perf->IsPlaying(dm_midi[id].dm_segment, NULL) == S_OK)
			dm_midi[id].state = MIDI_PLAYING;
		else
			dm_midi[id].state = MIDI_STOPPED;

		return(dm_midi[id].state);
	} // end if
	else
		return(0);

} // end DMusic_Status_MIDI

///////////////////////////////////////////////////////////

int DMusic_Init(void)
{
	// this function initializes directmusic, it also checks if directsound has
	// been initialized, if so it connect the wave output to directsound, otherwise
	// it creates it's own directsound object, hence you must start directsound up
	// first if you want to use both directsound and directmusic

	int index; // looping var

	// set up directmusic
	// initialize COM
	if (FAILED(CoInitialize(NULL)))
	{
		// Terminate the application.
		return(0);
	}   // end if

 // create the performance
	if (FAILED(CoCreateInstance(CLSID_DirectMusicPerformance,
		NULL,
		CLSCTX_INPROC,
		IID_IDirectMusicPerformance,
		(void**)&dm_perf)))
	{
		// return null        
		return(0);
	} // end if

 // initialize the performance, check if directsound is on-line if so, use the
 // directsound object, otherwise create a new one
	if (FAILED(dm_perf->Init(NULL, lpds, main_window_handle)))
	{
		return(0);// Failure -- performance not initialized
	} // end if 

 // add the port to the performance
	if (FAILED(dm_perf->AddPort(NULL)))
	{
		return(0);// Failure -- port not initialized
	} // end if

 // create the loader to load object(s) such as midi file
	if (FAILED(CoCreateInstance(
		CLSID_DirectMusicLoader,
		NULL,
		CLSCTX_INPROC,
		IID_IDirectMusicLoader,
		(void**)&dm_loader)))
	{
		// error
		return(0);
	} // end if

 // reset all the midi segment objects
	for (index = 0; index < DM_NUM_SEGMENTS; index++)
	{
		// reset the object
		dm_midi[index].dm_segment = NULL;
		dm_midi[index].dm_segstate = NULL;
		dm_midi[index].state = MIDI_NULL;
		dm_midi[index].id = index;
	} // end for index

// reset the active id
	dm_active_id = -1;

	// all good baby
	return(1);

} // end DMusic_Init

////////////////////////////////////////////////////////////

int DMusic_Shutdown(void)
{
	int index;

	// If there is any music playing, stop it. This is 
	// not really necessary, because the music will stop when
	// the instruments are unloaded or the performance is    
	// closed down.
	if (dm_perf)
		dm_perf->Stop(NULL, NULL, 0, 0);

	// delete all the midis if they already haven't been
	DMusic_Delete_All_MIDI();

	// CloseDown and Release the performance object.    
	if (dm_perf)
	{
		dm_perf->CloseDown();
		dm_perf->Release();
	} // end if

 // Release the loader object.
	if (dm_loader)
		dm_loader->Release();

	// Release COM
	CoUninitialize();

	// return success
	return(1);

} // end DMusic_Shutdown
